package dynamic.wrapper;

import com.baomidou.mybatisplus.core.conditions.SharedString;
import com.baomidou.mybatisplus.core.conditions.segments.MergeSegments;
import com.baomidou.mybatisplus.core.toolkit.*;
import com.github.yulichang.config.ConfigProperties;
import com.github.yulichang.toolkit.Constant;
import dynamic.DynamicEntity;
import dynamic.metadata.EntityClass;
import dynamic.metadata.EntityProperty;
import dynamic.mybatisplus.core.conditions.query.DWrapper;
import dynamic.mybatisplus.core.toolkit.DTableList;
import dynamic.toolkit.DJoinWrappers;
import dynamic.toolkit.DWrapperUtils;
import dynamic.toolkit.support.DColumnCache;
import dynamic.wrapper.interfaces.DQuery;
import dynamic.wrapper.interfaces.DQueryLabel;
import dynamic.wrapper.resultmap.DLabel;
import dynamic.wrapper.segments.*;
import lombok.Getter;

import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.stream.Collectors;


/**
 * 参考 {@link com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper}
 * Lambda 语法使用 Wrapper
 *
 * @author yulichang
 */
@SuppressWarnings({"unused", "DuplicatedCode"})
public class DMPJLambdaWrapper extends DMPJAbstractLambdaWrapper<DMPJLambdaWrapper> implements
        DQuery<DMPJLambdaWrapper>, DQueryLabel<DMPJLambdaWrapper>, DChain, DSelectWrapper<DMPJLambdaWrapper>{


    /**
     * 查询字段 sql
     */
    private SharedString sqlSelect = new SharedString();
    /**
     * 是否 select distinct
     */
    private boolean selectDistinct = false;
    /**
     * 查询的字段
     */
    @Getter
    private final List<DSelect> selectColumns = new ArrayList<>();
    /**
     * 映射关系
     */
    @Getter
    private final List<DLabel> resultMapMybatisLabel = new ArrayList<>();

    /**
     * union sql
     */
    private SharedString unionSql;

    /**
     * 自定义wrapper索引
     */
    private AtomicInteger wrapperIndex;

    /**
     * 自定义wrapper
     */
    @Getter
    private Map<String, DWrapper> wrapperMap;

    /**
     * 推荐使用 带 class 的构造方法
     */


    /**
     * 推荐使用此构造方法
     */
    public DMPJLambdaWrapper(EntityClass clazz) {
        super(clazz);
    }

    /**
     * 构造方法
     *
     * @param entity 主表实体
     */
    public DMPJLambdaWrapper(DynamicEntity entity) {
        super(entity);
    }

    /**
     * 自定义主表别名
     */
    public DMPJLambdaWrapper(String alias) {
        super(alias);
    }

    /**
     * 构造方法
     *
     * @param clazz 主表class类
     * @param alias 主表别名
     */
    public DMPJLambdaWrapper(EntityClass clazz, String alias) {
        super(clazz, alias);
    }

    public DMPJLambdaWrapper(DynamicEntity entity, String alias) {
        super(entity, alias);
    }



    /**
     * 不建议直接 new 该实例，使用 JoinWrappers.lambda(UserDO.class)
     */
    DMPJLambdaWrapper(DynamicEntity entity, EntityClass entityClass, SharedString sqlSelect, AtomicInteger paramNameSeq,
                      Map<String, Object> paramNameValuePairs, MergeSegments mergeSegments, SharedString paramAlias,
                      SharedString lastSql, SharedString sqlComment, SharedString sqlFirst,
                      DTableList tableList, Integer index, String keyWord, EntityClass joinClass, String tableName) {
        super(entityClass);
        super.setEntity(entity);
        super.setEntityClass(entityClass);
        this.paramNameSeq = paramNameSeq;
        this.paramNameValuePairs = paramNameValuePairs;
        this.expression = mergeSegments;
        this.sqlSelect = sqlSelect;
        this.paramAlias = paramAlias;
        this.lastSql = lastSql;
        this.sqlComment = sqlComment;
        this.sqlFirst = sqlFirst;
        this.tableList = tableList;
        this.index = index;
        this.keyWord = keyWord;
        this.joinClass = joinClass;
        this.tableName = tableName;
    }


    /**
     * sql去重
     * select distinct
     */
    public DMPJLambdaWrapper distinct() {
        this.selectDistinct = true;
        return typedThis;
    }


    @Override
    public List<DSelect> getSelectColumn() {
        return this.selectColumns;
    }

    @Override
    public void addLabel(DLabel label) {
        this.resultMap = true;
        this.resultMapMybatisLabel.add(label);
    }

    @Override
    public DMPJLambdaWrapper getChildren() {
        return typedThis;
    }




    /**
     * 设置查询字段
     *
     * @param columns 字段数组
     * @return children
     */

    public final <E> DMPJLambdaWrapper select(EntityProperty... columns) {
        if (ArrayUtils.isNotEmpty(columns)) {
            EntityClass entityClass = columns[0].getEntityClass();
            Map<String, DSelectCache> cacheMap = DColumnCache.getMapField(entityClass);
            for (EntityProperty s : columns) {
                DSelectCache cache = cacheMap.get(DColumnCache.getName(s));
                getSelectColumn().add(new DSelectNormal(cache, index, hasAlias, alias));
            }
        }
        return typedThis;
    }



    /**
     * 子查询
     */
    public <E, F> DMPJLambdaWrapper selectSub(EntityClass clazz, Consumer<DMPJLambdaWrapper> consumer, EntityProperty alias) {
        return selectSub(clazz, ConfigProperties.subQueryAlias, consumer, alias);
    }

    /**
     * 子查询
     */
    public <E, F> DMPJLambdaWrapper selectSub(EntityClass clazz, String st, Consumer<DMPJLambdaWrapper> consumer, EntityProperty alias) {
        DMPJLambdaWrapper wrapper = new DMPJLambdaWrapper(null, clazz, SharedString.emptyString(),
                paramNameSeq, paramNameValuePairs, new MergeSegments(), new SharedString(this.paramAlias
                .getStringValue()), SharedString.emptyString(), SharedString.emptyString(), SharedString.emptyString(),
                new DTableList(), null, null, null, null) {
        };
        wrapper.tableList.setAlias(st);
        wrapper.tableList.setRootClass(clazz);
        wrapper.tableList.setParent(this.tableList);
        wrapper.alias = st;
        wrapper.subTableAlias = st;
        consumer.accept(wrapper);
        addCustomWrapper(wrapper);
        this.selectColumns.add(new DSelectSub(() -> DWrapperUtils.buildSubSqlByWrapper(clazz, wrapper, alias.getPropertyName()), hasAlias, this.alias));
        return typedThis;
    }

    /**
     * union
     * <p>
     * 推荐使用 union(EntityClass clazz, Consumer<MPJLambdaWrapper> consumer)
     * 例： wrapper.union(EntityClass, union -> union.selectAll(UserDO.class))
     *
     * @see #union(EntityClass, Consumer)
     * @deprecated union 不支持子查询
     */
    @Deprecated
    @SuppressWarnings({"UnusedReturnValue"})
    public final DMPJLambdaWrapper union(DMPJLambdaWrapper... wrappers) {
        StringBuilder sb = new StringBuilder();
        for (DMPJLambdaWrapper wrapper : wrappers) {
            addCustomWrapper(wrapper);
            EntityClass entityClass = wrapper.getEntityClass();
            Assert.notNull(entityClass, "请使用 new MPJLambdaWrapper(主表.class) 或 JoinWrappers.lambda(主表.class) 构造方法");
            sb.append(" UNION ").append(DWrapperUtils.buildUnionSqlByWrapper(entityClass, wrapper));
        }
        if (Objects.isNull(unionSql)) {
            unionSql = SharedString.emptyString();
        }
        unionSql.setStringValue(unionSql.getStringValue() + sb);
        return typedThis;
    }

    /**
     * union
     * <p>
     * 例： wrapper.union(EntityClass clazz, union -> union.selectAll(UserDO.class))
     *
     * @param clazz union语句的主表类型
     * @since 1.4.8
     */
    public <U> DMPJLambdaWrapper union(EntityClass clazz, Consumer<DMPJLambdaWrapper> consumer) {
        DMPJLambdaWrapper unionWrapper = DJoinWrappers.lambda(clazz);
        addCustomWrapper(unionWrapper);
        consumer.accept(unionWrapper);

        String sb = " UNION " + DWrapperUtils.buildUnionSqlByWrapper(clazz, unionWrapper);

        if (Objects.isNull(unionSql)) {
            unionSql = SharedString.emptyString();
        }
        unionSql.setStringValue(unionSql.getStringValue() + sb);
        return typedThis;
    }

    /**
     * union
     * <p>
     * 推荐使用 unionAll(Class<U> clazz, Consumer<MPJLambdaWrapper<U>> consumer)
     * 例： wrapper.unionAll(UserDO.class, union -> union.selectAll(UserDO.class))
     *
     * @see #unionAll(EntityClass, Consumer)
     * @deprecated union 不支持子查询
     */
    @Deprecated
    @SuppressWarnings("DeprecatedIsStillUsed")
    public DMPJLambdaWrapper unionAll(DMPJLambdaWrapper... wrappers) {
        StringBuilder sb = new StringBuilder();
        for (DMPJLambdaWrapper wrapper : wrappers) {
            addCustomWrapper(wrapper);
            EntityClass entityClass = wrapper.getEntityClass();
            Assert.notNull(entityClass, "请使用 new MPJLambdaWrapper(主表.class) 或 JoinWrappers.lambda(主表.class) 构造方法");
            sb.append(" UNION ALL ").append(DWrapperUtils.buildUnionSqlByWrapper(entityClass, wrapper));
        }
        if (Objects.isNull(unionSql)) {
            unionSql = SharedString.emptyString();
        }
        unionSql.setStringValue(unionSql.getStringValue() + sb);
        return typedThis;
    }

    /**
     * union
     * <p>
     * 例： wrapper.unionAll(UserDO.class, union -> union.selectAll(UserDO.class))
     *
     * @param clazz union语句的主表类型
     * @since 1.4.8
     */
    public <U> DMPJLambdaWrapper unionAll(EntityClass clazz, Consumer<DMPJLambdaWrapper> consumer) {
        DMPJLambdaWrapper unionWrapper = DJoinWrappers.lambda(clazz);
        addCustomWrapper(unionWrapper);
        consumer.accept(unionWrapper);

        String sb = " UNION ALL " + DWrapperUtils.buildUnionSqlByWrapper(clazz, unionWrapper);

        if (Objects.isNull(unionSql)) {
            unionSql = SharedString.emptyString();
        }
        unionSql.setStringValue(unionSql.getStringValue() + sb);
        return typedThis;
    }

    private void addCustomWrapper(DMPJLambdaWrapper wrapper) {
        if (Objects.isNull(wrapperIndex)) {
            wrapperIndex = new AtomicInteger(0);
        }
        int index = wrapperIndex.incrementAndGet();
        if (Objects.isNull(wrapperMap)) {
            wrapperMap = new HashMap<>();
        }
        String key = "ew" + index;
        wrapper.setParamAlias(wrapper.getParamAlias() + ".wrapperMap." + key);
        wrapperMap.put(key, wrapper);
    }

    /**
     * 查询条件 SQL 片段
     */
    @Override
    public String getSqlSelect() {
        if (StringUtils.isBlank(sqlSelect.getStringValue()) && CollectionUtils.isNotEmpty(selectColumns)) {
            String s = selectColumns.stream().map(i -> {
                if (i.isStr()) {
                    return i.getColumn();
                }
                String prefix;
                if (i.isHasTableAlias()) {
                    prefix = i.getTableAlias();
                } else {
                    if (i.isLabel()) {
                        if (i.isHasTableAlias()) {
                            prefix = i.getTableAlias();
                        } else {
                            prefix = tableList.getPrefix(i.getIndex(), i.getClazz(), true);
                        }
                    } else {
                        prefix = tableList.getPrefix(i.getIndex(), i.getClazz(), false);
                    }
                }
                String str = prefix + StringPool.DOT + i.getColumn();
                if (i.isFunc()) {
                    DSelectFunc.Arg[] args = i.getArgs();
                    if (Objects.isNull(args) || args.length == 0) {
                        return String.format(i.getFunc().getSql(), str) + Constant.AS + i.getAlias();
                    } else {
                        return String.format(i.getFunc().getSql(), Arrays.stream(args).map(arg -> {
                            String prefixByClass = tableList.getPrefixByClass(arg.getClazz());
                            Map<String, DSelectCache> mapField = DColumnCache.getMapField(arg.getClazz());
                            DSelectCache cache = mapField.get(arg.getProp());
                            return prefixByClass + StringPool.DOT + cache.getColumn();
                        }).toArray()) + Constant.AS + i.getAlias();
                    }
                } else {
                    return i.isHasAlias() ? (str + Constant.AS + i.getAlias()) : str;
                }
            }).collect(Collectors.joining(StringPool.COMMA));
            sqlSelect.setStringValue(s);
        }
        return sqlSelect.getStringValue();
    }



    @Override
    public String getUnionSql() {
        return Optional.ofNullable(unionSql).map(SharedString::getStringValue).orElse(StringPool.EMPTY);
    }

    public boolean getSelectDistinct() {
        return selectDistinct;
    }

    /**
     * 用于生成嵌套 sql
     * <p>故 sqlSelect 不向下传递</p>
     */
    @Override
    protected DMPJLambdaWrapper instance() {
        return instance(index, null, null, null);
    }

    @Override
    protected DMPJLambdaWrapper instanceEmpty() {
        return new DMPJLambdaWrapper(getEntityClass());
    }

    @Override
    protected DMPJLambdaWrapper instance(Integer index, String keyWord, EntityClass joinClass, String tableName) {
        return new DMPJLambdaWrapper(getEntity(), getEntityClass(), null, paramNameSeq, paramNameValuePairs,
                new MergeSegments(), this.paramAlias, SharedString.emptyString(), SharedString.emptyString(), SharedString.emptyString(),
                this.tableList, index, keyWord, joinClass, tableName);
    }

    @Override
    public void clear() {
        super.clear();
        selectDistinct = false;
        sqlSelect.toNull();
        selectColumns.clear();
        wrapperIndex = new AtomicInteger(0);
        if (Objects.nonNull(wrapperMap)) wrapperMap.clear();
        if (Objects.nonNull(unionSql)) unionSql.toEmpty();
        resultMapMybatisLabel.clear();
    }



    public EntityClass getEntityClass(){
        return super.getEntityClass();
    }

   public DMPJLambdaWrapper setEntityClass(EntityClass clazz){
        super.setEntityClass(clazz);
        return this;
    }



//    default Children selectAll(EntityClass entityClass) {
//        getSelectColumn().addAll(DColumnCache.getListField(entityClass).stream().map(i ->
//                new DSelectNormal(i, getIndex(), isHasAlias(), getAlias())).collect(Collectors.toList()));
//        return getChildren();
//    }

//    Children selectAll(EntityClass clazz);
   @Override
   public DMPJLambdaWrapper  selectAll(EntityClass clazz){
     return   DQuery.super.selectAll(clazz);
   }


}
